/*
 * $Id: fsm_move.c,v 1.9 2005-11-03 12:42:33 bacon Exp $
 */

#include <xp/utl/fsm.h>
#include <xp/bas/errno.h>
#include <xp/bas/string.h>

static int __move (xp_fsm_t* fsm, 
	const xp_char_t* src, const xp_char_t* tgt, xp_stat_t* tstx)
{
	xp_stat_t sst, tst;
	xp_char_t lnked[XP_PATH_MAX + 1];

	if (xp_stat(src, &sst) == -1) {
		/* the source file does not exist */
		fsm->errnum = XP_FSM_ENOSRC;
		return -1;
	}

	if (tstx == XP_NULL && xp_stat(tgt, &tst) == -1) {
		if (xp_errno != XP_ENOENT) return -1;

		/* target does not exist */
		if (xp_rename(src, tgt) == 0) return 0;
		if (xp_errno != XP_EXDEV) {
			fsm->errnum = 
				(xp_errno == XP_EPERM || xp_errno == XP_EACCES)?
				XP_FSM_EACCES: XP_FSM_ERENAM;
			return -1;
		}

		if (xp_fsm_copy (fsm, src, tgt) == -1) return -1;
		if (xp_unlink (src) == -1) {
			fsm->errnum = XP_FSM_EUNLNK;
			return -1;
		}

		return 0;
	}
	else {
		if (tstx == XP_NULL) tst = *tstx;

		/* target exists */
		if (sst.st_dev == tst.st_dev &&
		    sst.st_ino == tst.st_ino) {
			fsm->errnum = XP_FSM_ESAMFL;
			return -1;
		}
		
		if (S_ISLNK(tst.st_mode)) {
			if (xp_readlink(tgt, lnked, xp_countof(lnked)) == 0) {
				xp_stat_t tst2;
				if (xp_stat(lnked, &tst2) == 0) {
					tst = tst2;
					tgt = lnked;		
				}
			}
		}

		if (S_ISDIR(tst.st_mode)) {
			/* cannot change a file to a directory */
			fsm->errnum = XP_FSM_EINVAL;
			return -1;
		}	
		else {
			if (fsm->confirmer != XP_NULL &&
			    fsm->confirmer (XP_FSM_CONFIRM_OVERWRITE, fsm->confirmer_arg, tgt) == -1) {
				fsm->errnum = XP_FSM_ECANCL;
				return -1;
			}

			/* do not care about the result of xp_unlink
			 * because if there is something wrong, xp_rename
			 * is bound to fail as well */
			xp_unlink (tgt); 
			if (xp_rename(src, tgt) == 0) return 0;
			
			if (xp_errno != XP_EXDEV) {
				fsm->errnum = 
					(xp_errno == XP_EPERM || xp_errno == XP_EACCES)?
					XP_FSM_EACCES: XP_FSM_ERENAM;
				return -1;
			}

			if (xp_fsm_copy (fsm, src, tgt) == -1) return -1;
			if (xp_unlink (src) == -1) {
				fsm->errnum = XP_FSM_EUNLNK;
				return -1;
			}
			return 0;
		}	
	}

	return 0;
}

int xp_fsm_move (xp_fsm_t* fsm, const xp_char_t* src, const xp_char_t* tgt)
{
	xp_char_t rtgt[XP_PATH_MAX + 1 + 1];
	xp_stat_t rtst, * tst = XP_NULL;
	xp_size_t n;

	if (xp_stat(tgt, &rtst) == 0) {
		if (S_ISDIR(rtst.st_mode)) {
			/* target exists and is a directory */
			n = xp_strxapp (rtgt, xp_countof(rtgt), 
				tgt, XP_TEXT("/"), src, XP_NULL);
			if (n > XP_PATH_MAX) {
				/* invalid path name */
				fsm->errnum = XP_FSM_EINVAL;
				return -1;
			}
			tgt = rtgt;
		}
		else tst = &rtst;
	}

	return __move (fsm, src, tgt, tst);
}
